package com.crawler.waf.security.services.impl;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.util.Assert;

import com.crawler.waf.config.WafProperties;
import com.crawler.waf.exceptions.extendExceptions.WafSimpleException;
import com.crawler.waf.security.authens.UserToken;
import com.crawler.waf.security.authentication.mac.PreAuthenticatedMacTokenAuthentication;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

public class UserCenterMacTokenCacheService extends UserCenterMacTokenService {

    private static LoadingCache<PreAuthenticatedMacTokenAuthentication, UserToken> macTokenCache;

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    public static final String MAC_TOKEN_CACHE_EXPIRE = "macToken.cache.expire";
    public static final String MAC_TOKEN_CACHE_MAX_SIZE = "macToken.cache.maxSize";

    /**
     * 初始化mac cache
     */
    private void initMacCache() {
        if (macTokenCache == null) {
            synchronized (this) {
                if (macTokenCache == null) {
                    macTokenCache = CacheBuilder
                            .newBuilder().maximumSize(WafProperties.getPropertyForInteger(MAC_TOKEN_CACHE_MAX_SIZE,"10000"))
                            .expireAfterWrite(WafProperties.getPropertyForInteger(MAC_TOKEN_CACHE_EXPIRE, "120"), TimeUnit.MINUTES)
                            .build(new CacheLoader<PreAuthenticatedMacTokenAuthentication, UserToken>() {
                                @Override
                                public UserToken load(PreAuthenticatedMacTokenAuthentication key) throws Exception {
                                    return invoke(key);
                                }
                            });
                }
            }
        }
    }

    /**
     * 调用父类获取verifyToken方法
     *
     * @param preAuthenticatedAuthentication
     * @return
     */
    private UserToken invoke(Authentication preAuthenticatedAuthentication) {
        return super.verifyToken(preAuthenticatedAuthentication);
    }

    @Override
    public UserToken verifyToken(Authentication preAuthenticatedAuthentication) {
        Assert.notNull(preAuthenticatedAuthentication, "preAuthenticatedAuthentication");
        PreAuthenticatedMacTokenAuthentication authentication = (PreAuthenticatedMacTokenAuthentication) preAuthenticatedAuthentication;
        //初始化缓存实现
        this.initMacCache();
        UserToken checkToken = null;

        try {
        	checkToken = macTokenCache.get(authentication);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }

        Assert.notNull(checkToken, "checkToken");
        return this.checkMac(checkToken, authentication);
    }

    public UserToken checkMac(UserToken checkToken, PreAuthenticatedMacTokenAuthentication authRequest) {
    	Assert.notNull(checkToken, "checkToken");
    	Assert.notNull(authRequest, "authRequest");
        StringBuilder sbRawMac = new StringBuilder();
        sbRawMac.append(authRequest.getNonce());
        sbRawMac.append("\n");
        sbRawMac.append(authRequest.getHttpMethod().toUpperCase());
        sbRawMac.append("\n");
        sbRawMac.append(authRequest.getRequestUri());
        sbRawMac.append("\n");
        sbRawMac.append(authRequest.getHost());
        sbRawMac.append("\n");
        if (logger.isDebugEnabled()) {
            logger.debug("Mac key:" + authRequest.getMac());
        }
        String newMac = encryptHMac256(sbRawMac.toString(), checkToken.getMacKey());
        if (logger.isDebugEnabled()) {
            logger.debug("newMac:" + newMac);
        }
        if (!authRequest.getMac().equalsIgnoreCase(newMac)) {
        	logger.error(authRequest.getMac() + ":" + sbRawMac.toString());
        	throw new WafSimpleException("授权校验失败");
        }
        return checkToken;
    }
    
    public static String encryptHMac256(String content, String key) {
    	Assert.notNull(content, "content");
    	Assert.notNull(key, "key");
        // 还原密钥
        SecretKey secretKey = new SecretKeySpec(key.getBytes(), "HmacSHA256");
        // 实例化Mac
        Mac mac = null;
        try {
            mac = Mac.getInstance(secretKey.getAlgorithm());
            mac.init(secretKey);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        // 执行消息摘要
        byte[] digest = mac.doFinal(content.getBytes());
        return new String(Base64.encode(digest));
    }
}
